package com.bupt.uchouten.common.skill;

import com.bupt.uchouten.common.entity.EarthquakeEntity;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;

import javax.annotation.Nullable;
import java.util.function.Function;

public class SkillGenerator extends Skill {

    /**
     * A factory that creates generator entities.
     */
    protected final Function<Level, EarthquakeEntity> generatorFactory;
    /**
     * Whether the generator lasts indefinitely, i.e. does not disappear after a set time.
     */
    protected final boolean permanent;
    /**
     * Whether the generator must be spawned on the ground. Defaults to false.
     */
    protected boolean requiresFloor = false;
    /**
     * Whether generators spawned by this skill may overlap. Defaults to false.
     */
    protected boolean allowOverlap = false;

    public SkillGenerator(Function<Level, EarthquakeEntity> generatorFactory, boolean permanent) {
        super();
        this.generatorFactory = generatorFactory;
        this.permanent = permanent;
    }

    /**
     * Sets whether the generator must be spawned on the ground.
     *
     * @param requiresFloor True to require that the generator be spawned on the ground, false to allow it in mid-air.
     *                      Defaults to false.
     * @return The skill instance, allowing this method to be chained onto the constructor.
     */
    public SkillGenerator floor(boolean requiresFloor) {
        this.requiresFloor = requiresFloor;
        return this;
    }

    /**
     * Sets whether generators spawned by this skill may overlap.
     *
     * @param allowOverlap True to allow overlapping, false to prevent it. Defaults to false.
     * @return The skill instance, allowing this method to be chained onto the constructor.
     */
    public SkillGenerator overlap(boolean allowOverlap) {
        this.allowOverlap = allowOverlap;
        return this;
    }

    @Override
    public boolean cast(Level world, Player caster) {
        if (caster.onGround() || !requiresFloor) {
            if (!spawnGenerator(world, caster.getX(), caster.getY(), caster.getZ(), caster.onGround() ? Direction.UP : null, caster)) {
                return false;
            }
            this.playSound(world, caster);
            return true;
        }

        return false;
    }

    /**
     * Actually spawns the generator. By default, spawns the generator at the position of the caster and always returns
     * true. Returning false will cause the skill to fail.
     *
     * @param world  The world to spawn the generator in.
     * @param x      The x coordinate to spawn the generator at.
     * @param y      The y coordinate to spawn the generator at.
     * @param z      The z coordinate to spawn the generator at.
     * @param side   The side of a block that was hit, or null if the generator is being spawned in mid-air (only happens
     *               if {@link SkillGenerator#requiresFloor} is true).
     * @param caster The EntityLivingBase that cast this skill.
     * @return false to cause the skill to fail, true to continue with casting.
     */
    protected boolean spawnGenerator(Level world, double x, double y, double z, @Nullable Direction side, @Nullable LivingEntity caster) {
        if (!world.isClientSide()) {
            // Creates a new generator using the supplied factory
            EarthquakeEntity generator = generatorFactory.apply(world);
            // Sets the position of the generator (and initialises its bounding box)
            generator.setPos(x, y, z);
            // Sets the various parameters
            generator.setCaster(caster);
            generator.lifetime = -1;
            generator.damageMultiplier = 100;
            addGeneratorExtras(generator, side, caster);
            // Prevents overlapping of multiple generators of the same type. Since we have an instance here this is
            // very simple. The trade-off is that we have to create the entity before the skill fails, but unless
            // world.addFreshEntity(generator) is called, its scope is limited to this method so it should be fine.
            // Needs to be last in case addGeneratorExtras modifies the bounding box
            if (!allowOverlap && !world.getEntitiesOfClass(generator.getClass(), generator.getBoundingBox()).isEmpty()) {
                return false;
            }
            // Spawns the generator in the world
            world.addFreshEntity(generator);
        }

        return true;
    }

    /**
     * Called just before each generator is spawned. Does nothing by default, but is provided to allow subclasses to call
     * extra methods on the spawned entity. This method is only called server-side so cannot be used to spawn particles
     * directly.
     * <p></p>
     * This is the reason this class is generic: it allows subclasses to do whatever they want to their specific entity,
     * without needing to cast to it.
     *
     * @param generator The entity being spawned.
     * @param side      The side of a block that was hit, or null if the generator is being spawned in mid-air (only happens
     *                  if {@link SkillGenerator#requiresFloor} is true).
     * @param caster    The caster of this skill, or null if it was cast by a dispenser.
     */
    protected void addGeneratorExtras(EarthquakeEntity generator, Direction side, @Nullable LivingEntity caster) {
    }

}
